import calendar
import csv
import os
from datetime import datetime, date
from itertools import chain

from flask import render_template, request
from sqlalchemy import select, update, func, delete

from models.create_tables_and_insert_data import session_factory
from models.book import Book
from models.author import Author
from models.receiving_book import ReceivingBook
from models.student import Student
from app import app


@app.route('/')
def index():
    return render_template("index.html")


@app.route('/books')
def get_all_books():
    # эндпойнт для вывода всех книг из базы данных
    with session_factory() as session:
        result = session.execute(select(Book))
        books = result.all()
        books_list = []
        for book in books:
            books_list.append(book[0].to_json())
        return render_template("books_list.html", books=books_list)


@app.route('/authors')
def get_authors():
    # эндпойнт для вывода всех авторов книг из БД
    with session_factory() as session:
        result = session.execute(select(Author))
        authors = result.unique().all()
        authors_list = []
        for author in authors:
            authors_list.append(author[0].to_json())
        return render_template("authors_list.html", authors=authors_list)


@app.route('/add_book', methods=['GET', 'POST'])
def add_book():
    # эндпойнт для добавления новой записи книги
    with session_factory() as session:
        if request.method == 'POST':
            name = request.form.get('name', type=str)
            count = request.form.get('count', type=int)
            release_date = datetime.strptime(request.form.get('release_date'), '%Y-%m-%d')
            author_id = request.form.get('author_id', type=int)
            new_book = Book(title=name, count=count, release_date=release_date, author_id=author_id)
            session.add(new_book)
            session.commit()
            return (f"Новая запись книги {name} создана!"
                    f'<br><a href="/">Вернуться на главную страницу</a><br>')

        # передадим авторов книг для вставки в выпадающий список html-формы
        result = session.execute(select(Author))
        authors = result.unique().all()
        authors_list = []
        for author in authors:
            authors_list.append(author[0].to_json())
        return render_template("add_book.html", authors=authors_list)


@app.route('/add_author', methods=['GET', 'POST'])
def add_author():
    if request.method == 'POST':
        with session_factory() as session:
            first_name = request.form.get('name', type=str)
            last_name = request.form.get('surname', type=str)
            new_author = Author(first_name=first_name, last_name=last_name)
            session.add(new_author)
            session.commit()
        return (f'Запись нового автора ({first_name} {last_name}) успешно создана'
                f'<br><a href="/">Вернуться на главную...</a>')

    return render_template("add_author.html")


@app.route('/add_student', methods=['GET', 'POST'])
def add_student():
    with session_factory() as session:
        if request.method == 'POST':
            # создание новой записи студента
            name = request.form.get('name', type=str)
            surname = request.form.get('surname', type=str)
            phone = request.form.get('phone', type=str)
            email = request.form.get('email', type=str)
            average_score = request.form.get('average_score', type=int)
            select_yes_no = request.form.get('scholarship')
            if select_yes_no == 'yes':
                scholarship = True
            else:
                scholarship = False
            new_student = Student(
                first_name=name, last_name=surname, phone=phone, email=email,
                average_score=average_score, scholarship=scholarship
            )
            session.add(new_student)
            session.commit()
            return (f'Запись нового студента ({name} {surname}) успешно создана'
                    f'<br><a href="/">Вернуться на главную...</a>')

        return render_template("add_student.html")


@app.route('/take_book', methods=['GET', 'POST'])
def get_take_book():
    with session_factory() as session:
        if request.method == 'POST':
            # возьмём из формы полученные данные
            student_id = request.form.get("student", type=int)
            book_id = request.form.get('book', type=int)
            # добавляем в базу данных новую запись о взятии книги
            take_book = ReceivingBook(book_id=book_id, student_id=student_id, )
            session.add(take_book)

            # получаем запись книги, которую хочет взять студент
            book = session.query(Book).filter(Book.id == book_id).one_or_none()
            # убавляем счетчик количества взятой книги на -1
            session.execute(update(Book).filter(Book.id == book_id).values(count=book.count - 1))
            session.commit()

            return '<h3>Вы взяли книгу</h3><br><a href="/">Вернуться на главную страницу</a><br>'

        # Передадим в форму данные студентов и книг
        result = session.execute(select(Student.id, Student.first_name, Student.last_name))
        students = result.unique().all()
        students_list = []
        for student in students:
            students_list.append({"id": student[0], "first_name": student[1], "last_name": student[2]})

        result = session.execute(select(Book.id, Book.title, Book.count))
        books = result.unique().all()
        books_list = []
        for book in books:
            books_list.append({"id": book[0], "title": book[1], "count": book[2]})

        return render_template('take_book.html', students=students_list, books=books_list)


@app.route('/return_book', methods=['GET', 'POST'])
def return_book():
    # Возврат книги в библиотеку
    with (session_factory() as session):
        if request.method == 'POST':
            book_id = request.form.get('book', type=int)
            student_id = request.form.get('student', type=int)

            # Проверяем, есть ли такая связка book_id - student_id в таблице ReceivingBooks. Нужен только id
            book_taken = session.execute(select(ReceivingBook.id).filter(
                ReceivingBook.book_id == book_id, ReceivingBook.student_id == student_id
            )).unique().one_or_none()
            if book_taken:
                # Получаем текущее количество книг с запрошенным id
                books_count = session.execute(select(Book.count).filter(Book.id == book_id)).scalar()
                # Увеличиваем счетчик данной книги +1
                session.execute(update(Book).filter(Book.id == book_id).values(count=books_count + 1))
                # Ставим в поле date_of_return сегодняшнюю дату возврата книги
                session.execute(update(ReceivingBook).filter(
                    ReceivingBook.id == book_taken[0]).values(date_of_return=datetime.now()))
                session.commit()
                return '<h3>Вы сдали книгу</h3><br><a href="/">Вернуться на главную страницу</a><br>'
            else:
                return ('<br><br><h3>Такой связки (book_id - student_id) не существует. '
                        'Попробуйте изменить один из параметров.</h3>'
                        '<br><br><a href="/return_book">Вернуться назад</a><br>')

        # Получаем id, имена и фамилии тех студентов, которые брали книги
        result = session.execute(
            select(Student.id, Student.first_name, Student.last_name).where(
                Student.id.in_(list(chain(*session.execute(select(ReceivingBook.student_id)
                                                           .filter(ReceivingBook.date_of_return == None)).all()))))
        )
        students = result.all()
        students_list = []
        for student in students:
            students_list.append({"id": student[0], "first_name": student[1], "last_name": student[2]})

        # получаем список книг взятых студентами
        result = session.execute(
            select(Book.id, Book.title).where(
                Book.id.in_(list(chain(*session.execute(select(ReceivingBook.book_id)
                                                        .filter(ReceivingBook.date_of_return == None)).all()))))
        )
        books = result.all()
        books_list = []
        for books_count in books:
            books_list.append({"id": books_count[0], "title": books_count[1]})

        return render_template('return_book.html', students=students_list, books=books_list)


@app.route('/find_book', methods=['GET', 'POST'])
def find_book():
    # Поиск книги по названию
    if request.method == "POST":
        with session_factory() as session:
            tag = request.form.get('search', type=str).capitalize()
            search = "%{}%".format(tag)
            books = session.execute(select(Book).filter(Book.title.like(search))).unique().all()
            books_list = []
            for book in books:
                books_list.append(book[0].to_json())
        return render_template('found_books.html', books=books_list)
    return render_template("find_book.html")

# --------------------------------------------------------------------------------------------------------


@app.route('/books_by_author', methods=['GET', 'POST'])
def get_remains_books_by_author():
    # 1. Получите количество оставшихся в библиотеке книг по автору (GET — входной параметр — ID автора)
    with session_factory() as session:
        if request.method == 'POST':
            author_id = request.form.get('author_id', type=int)
            result = session.execute(select(Book.count).filter_by(author_id=author_id)).all()
            remains_books = 0
            for count in result:
                remains_books += count[0]
            return render_template(
                "show_remaining_books_by_author.html",
                count=remains_books, id=author_id)
        authors = session.execute(select(Author)).unique().all()
        authors_list = []
        for author in authors:
            authors_list.append(author[0].to_json())
        return render_template('select_author.html', authors=authors_list)


@app.route('/book_recommendation', methods=['GET', 'POST'])
def get_book_recommendation():
    """
    получите список книг, которые студент не читал, при этом другие книги этого автора студент уже брал
    (GET — входной параметр — ID студента).
    """
    with session_factory() as session:
        if request.method == 'POST':
            student_id = request.form.get('student_id', type=int)

            # 1. Получаем id книг, которые студент читал (id-студента = 1, книги (1, 2, 3, 5) Три книги Льва Толстого
            #       и одна книга М.Булгакова
            # 2. Получаем id авторов, которые эти книги написали [(1,), (1,), (2,), (1,)]
            # 3. Получаем рекомендованные книги которые студент не читал, тех авторов, которых читал
            books = session.query(ReceivingBook.book_id).filter_by(student_id=student_id)
            authors = session.query(Book.author_id).where(Book.id.in_(books))
            recommend_books = session.query(Book).where(Book.id.not_in(books)).where(Book.author_id.in_(authors))
            books_list = []
            books = recommend_books.all()
            for book in books:
                books_list.append(book.to_json())

            return render_template("book_recommendation.html", books=books_list)

        # Получаем студентов, которые брали книги
        result = session.execute(
            select(Student).where(
                Student.id.in_(list(chain(*session.execute(select(ReceivingBook.student_id)).all()))))
        )
        students = result.unique().all()
        students_list = []
        for student in students:
            students_list.append(student[0].to_json())

        return render_template("select_student.html", students=students_list)


@app.route('/count_books', methods=['GET', 'POST'])
def get_count_books_in_month():
    # вывести количество книг, взятых студентами, в каком-либо месяце
    if request.method == 'POST':
        month = request.form.get('month', type=int)
        days = calendar.monthrange(datetime.now().year, month)[1]
        with session_factory() as session:
            books = session.execute(select(ReceivingBook).filter(
                ReceivingBook.date_of_issue.between(
                    date(2024, month, 1), date(2024, month, days))
            )).unique().all()
        return f"<h3>Количество книг, которые студенты брали в {month} месяце: {len(books)}</h3>"

    months_list = list(range(1, datetime.now().month + 1))
    return render_template("select_month.html", months=months_list)


@app.route('/popular_book')
def get_popular_book():
    # Показать самую популярную книгу среди студентов, у которых средний балл больше 4.0
    with (session_factory() as session):
        books = session.query(
            ReceivingBook.book_id, func.count(ReceivingBook.book_id)
        ).where(ReceivingBook.student_id.in_(
            session.query(Student.id).where(Student.average_score > 4))).group_by(ReceivingBook.book_id).all()

        book_id = max(books, key=lambda book: book[1])[0]
        book_title = session.execute(select(Book.title).filter_by(id=book_id)).first()
    return (f"<h3>Самая популярная книга: {book_title[0]}</h3><br>"
            f"<a href='/'>Вернуться на главную страницу</a>")


@app.route('/top_ten')
def get_top_ten_students():
    # Топ 10 самых читающих студентов в 2024 году
    with (session_factory() as session):
        students = session.query(Student).where(
            Student.id.in_(session.query(ReceivingBook.student_id).filter(
                ReceivingBook.date_of_issue.between(date(2024, 1, 1), date(2024, 12, 31))
            ).group_by(
                ReceivingBook.student_id).order_by(func.count(ReceivingBook.book_id).desc()).limit(10))).all()
        students_list = []
        for student in students:
            students_list.append(student.to_json())
    return render_template('top_ten_students.html', students=students_list)


@app.route('/load_csv_file', methods=['GET', 'POST'])
def load_csv_file():
    # Загрузка csv-файла, чтение и запись данных в БД
    if request.method == "POST":
        file = request.files['csv_file']
        file.save(os.path.join(file.filename))
        with open(file.filename, "r") as filename:
            fieldnames = ['first_name', 'last_name', 'phone', 'email']
            read_file = csv.DictReader(f=filename, fieldnames=fieldnames, delimiter=";")
            dict_list = []
            for string_ in read_file:
                dict_list.append(string_)
            with session_factory() as session:
                session.bulk_insert_mappings(Student, dict_list)
                session.commit()
        return ("Данные о студентах загружены. "
                '<a href="/">Возврат на главную страницу</a>')
    return render_template("load_csv_file.html")


@app.route('/delete', methods=['GET', 'POST'])
def delete_author():
    # Эндпойнт для проверки каскадного удаления. Если удаляем автора книги то и книги и прочие
    # связи должны удалиться
    with session_factory() as session:
        if request.method == 'POST':
            author_id = request.form.get('author_id', type=int)
            # session.query(Author).filter(Author.id == author_id).delete()
            session.execute(delete(Author).filter(Author.id == author_id))
            session.commit()

        authors = session.execute(select(Author)).unique().all()
        authors_list = []
        for author in authors:
            authors_list.append(author[0].to_json())
        return render_template("select_author.html", authors=authors_list)
